package com.google.code.netcheckin.xastir;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;

/**
  * A class that can be used to send APRS packets to the 
  * UDP server port of xastir.  
  * Typical usage: instantiate, configure, use sendPacket
  * to send packets, close.  
  * A static main method provides command-line usage that is similar
  * to xastir's xastir_udp_client.
  * @author Dick Repasky
  *
*/

public class XastirUdpClient {

	public static final int DEFAULT_XASTIR_PORT = 2023;
	public static final int SOCKET_TIMEOUT_MILLIS = 5000;

		/** Code that reflects xastir's response. */
	public static final int XASTIR_ACK	= 0;
		/** Code that reflects xastir's response. */
	public static final int XASTIR_NACK	= 1;

		/** Answer returned by xastir server. */
	protected static final String XASTIR_ACK_LINE = "ACK";
		/** Answer returned by xastir server. */
	protected static final String XASTIR_NACK_LINE = "NACK";


		/** Option accepted from command line (static main). */
	protected static final String TO_IGATE_STRING = "-to_igate";
		/** Option passed to xastir. */
	protected static final byte[] TO_IGATE = TO_IGATE_STRING.getBytes();
		/** Option accepted from command line (static main). */
	protected static final String TO_RF_STRING = "-to_rf";
		/** Option passed to xastir. */
	protected static final byte[] TO_RF = TO_RF_STRING.getBytes();

		/** Socket used to communicate with xastir server. */
	protected DatagramSocket socket;
		/** Recepticle for incoming UDP packets. */
	protected DatagramPacket receivePacket;

		/** Address of xastir server. */
	protected InetAddress xastirAddress;
		/** Xastir server port. */
	protected int xastirPort = DEFAULT_XASTIR_PORT;

		/** Configuration: user id passed to xastir. */
	protected byte[] xastirUserId;

		/** Configuration: password passed to xastir. */
 	protected byte[] xastirPassword;

		/** Buffer used to assemble UDP packets destined 
		  * for xastir. */
	protected byte[] xastirBuffer = new byte[1024];
		/** Buffer used in incoming DatagramPackets. */
	protected byte[] readBuffer = new byte[2048];

		/** Configuration: request xastir forward packets to its igate. */
	protected boolean toXastirIgate = false;
		/** Configuration: request xastir forward packts to RF. */
	protected boolean toXastirRf = false;

		/** Defaults to localhost and DEFAULT_XASTIR_PORT.  Use 
		  * accessor methods to supply xastir userid and password. */
	public XastirUdpClient() throws UnknownHostException, SocketException {
		this("localhost", DEFAULT_XASTIR_PORT);
	}

		/** Use accessor methods to supply xastir userid and password. */
	public XastirUdpClient(String host, int port) 
				throws UnknownHostException, SocketException  {
		xastirAddress = InetAddress.getByName(host);
		xastirPort = port;
		socket = new DatagramSocket();
		socket.setSoTimeout(SOCKET_TIMEOUT_MILLIS);
		receivePacket = new DatagramPacket(readBuffer, 
				readBuffer.length);
	}

	public XastirUdpClient(String host, int port, String uname, String password) 
				throws UnknownHostException, SocketException {
		this(host, port);
		xastirUserId = uname.getBytes();
		xastirPassword = password.getBytes();
	}

	public void close() throws IOException {
		socket.close();
	}

		/**
 		  * Socket timeout when waiting for response.
 		  * @return timeout in milliseconds
 		  *
 		*/

	public int getSoTimeout() throws SocketException {
		return socket.getSoTimeout();
	}

	public String getXastirPassword() {
		return new String(xastirPassword);
	}

	public String getXastirUserId() {
		return new String(xastirUserId);
	}

		/**
  		  * Send a packet that has been entirely comosed.
  		  * @throws java.io.IOException - if the exception is
  		  * an instance of java.net.SocketTimeoutException
  		  * it probably means that xastir's server ports are
  		  * not open.
  		  *
  		*/

	public int sendPacket(byte[] wholepacket) throws IOException {
		DatagramPacket udppacket = new DatagramPacket(wholepacket,
			wholepacket.length, xastirAddress, xastirPort);
		return send(udppacket);
	}

		/**
  		  * Compose a packet from sender, destination address,
  		  * path and APRS payload and send it to xastir.
  		  * @throws java.io.IOException - if the exception is
  		  * an instance of java.net.SocketTimeoutException
  		  * it probably means that xastir's server ports are
  		  * not open.
  		  *
  		*/

	public int sendPacket(byte[] fromaddr, byte[] toaddr, byte[] path,
			byte[] payload) throws IOException {
		int packlen = assembleXastirPacket(fromaddr, toaddr, 
				path, payload);
		DatagramPacket udppacket = new DatagramPacket(xastirBuffer,
				packlen, xastirAddress, xastirPort);
		return send(udppacket);
	}

		/**
 		  * Set socket timeout when waiting for a response.
 		  * @param millisec - timeout in milliseconds.
 		  *
 		*/

	public void setSoTimeout(int millisec) throws SocketException {
		socket.setSoTimeout(millisec);
	}

	public void setToXastirIgate(boolean to) {
		toXastirIgate = to;
	}

	public void setToXastirRf(boolean to) {
		toXastirRf = to;
	}

	public void setXastirPassword(String pw) {
		setXastirPassword(pw.getBytes());
	}

	public void setXastirPassword(byte[] pw) {
		xastirPassword = pw;
	}

	public void setXastirUserId(String userid) {
		setXastirUserId(userid.getBytes());
	}

	public void setXastirUserId(byte[] userid) {
		xastirUserId = userid;
	}

		/** A static main that allows you to use this class
		  * just like xastir's xastir_udp_client. Great for testing.
		  */
	public static void main(final String[] argv) throws IOException {
		if (argv.length < 5) {
			printUsage();
			return;
		}
		boolean toigate = false;
		boolean torf = false;
		String packetdata = null;
		for (int idx = 4; idx < argv.length; idx ++) {
			if (TO_IGATE_STRING.equals(argv[idx])) {
				toigate = true;
			} else if (TO_RF_STRING.equals(argv[idx])) {
				torf = true;
			} else {
				packetdata = argv[idx];
				break;
			}
		}
		XastirUdpClient client = new XastirUdpClient(argv[0],
			Integer.parseInt(argv[1]), argv[2], argv[3]);
		int rc = client.send(packetdata.getBytes());
		System.out.println(
		   rc == XASTIR_ACK ? XASTIR_ACK_LINE : XASTIR_NACK_LINE);
		client.close();
		System.exit(rc);
	}

	////////////////////// protected below ///////////////////////////

		/**
		  * Assemble APRS packet data in a buffer
		  * provided begining at offset destidx in the
		  * buffer.  Use destidx = 0 to begin at the start of
		  * the buffer.  
		  * @param destbuf buffer to fill
		  * @param destidx offset at which to begin filling
		  * @param address of APRS sender
		  * @param address of APRS receiver
		  * @param APRS path
		  * @param APRS payload (object, station report etc.)
		  * @return the index of the next available slot in the buffer.
		  *
		*/
	protected int assembleAPRSPacket(byte[] destbuf, int destidx,
			byte[] fromaddr, byte[] toaddr, byte[] path,
			byte[] payload) {
		destidx += fill(destbuf, destidx, fromaddr, 0, fromaddr.length);
		destbuf[destidx] = '>';
		destidx ++;
		destidx += fill(destbuf, destidx, toaddr, 0, toaddr.length);
		if (path != null && path.length > 0) {
			destbuf[destidx] = ',';
			destidx ++;
			destidx += fill(destbuf, destidx, path, 0, path.length);
		}
		destbuf[destidx] = ':';
		destidx ++;
		destidx += fill(destbuf, destidx, payload, 0, payload.length);
		return destidx;
	}

		/**
		  * Assemble a byte string that is ready to send to
		  * xastir as a UDP packet. The result is in xastirBuffer.
		  * @return the length of the packet, which should never
		  * exceed the length of the buffer.
		*/
	protected int assembleXastirPacket(byte[] fromaddr, byte[] toaddr,
			byte[] path, byte[] payload) {
		int bufidx = placeXastirFields();
		bufidx = assembleAPRSPacket(xastirBuffer, bufidx, fromaddr, toaddr, path,
				payload);
		xastirBuffer[bufidx] = '\n';
		bufidx ++;
		return bufidx;
	}

		/**
		  * Assemble a complete UDP packet given a complete 
		  * APRS packet (that includes addressing and path.
		  * Result is in xastirBuffer.
		  * @return length of packet.
	          *
		*/

	protected int assembleXastirPacket(byte[] completepacket) {
		int bufidx = placeXastirFields();	// host, port, call, password
							// APRS data
		bufidx += fill(xastirBuffer, bufidx, completepacket, 0, completepacket.length);
		xastirBuffer[bufidx] = '\n';
		bufidx ++;
		return bufidx;
	}

		/**
		  * Copy bytes into a destination, beginning at given 
		  * offset, from a source, given an offset and endpoint
		  * in the source.
		  *
		*/

	protected int fill(byte[] target, int targidx,
			byte[] source, int srcstart, int srcend) {
		for(int srcidx = srcstart; srcidx < srcend; srcidx ++) {
			target[targidx] = source[srcidx];
			targidx ++;
		}
		return srcend - srcstart;
	}

		/**
		  * Assemble xastir user id, xastir password, and
		  * xastir options in the outgoing packet buffer
		  * (xastirBuffer).
		  * @return current length.
		  */

	protected int placeXastirFields() {
		int bufidx = 0;
		bufidx += fill(xastirBuffer, bufidx, xastirUserId, 0, 
					xastirUserId.length);
		xastirBuffer[bufidx] = ',';
		bufidx ++;
		bufidx += fill(xastirBuffer, bufidx, xastirPassword, 0,
					xastirPassword.length);
		if (toXastirIgate) {
			xastirBuffer[bufidx] = ',';
			bufidx ++;
			bufidx += fill(xastirBuffer, bufidx, TO_IGATE, 0,
					TO_IGATE.length);
		}
		if (toXastirRf) {
			xastirBuffer[bufidx] = ',';
			bufidx ++;
			bufidx += fill(xastirBuffer, bufidx, TO_RF, 0, TO_RF.length);
			xastirBuffer[bufidx] = ' ';
			bufidx ++;
		}
		xastirBuffer[bufidx] = '\n';
		bufidx ++;
		return bufidx;
	}

		/** Send a packet that already contains addressing,
		  * path and APRS payload. */

	protected int send(byte[] completepacket) throws IOException {
		int packlen = assembleXastirPacket(completepacket);
		return send(new DatagramPacket(xastirBuffer, packlen,
				xastirAddress, xastirPort));
	}



	//////////// private below /////////////////////////////////////

		/** Send a completely assembled UDP packet to xastir.
		  * @return XASTIR_ACK or XASTIR_NACK. 
		  * @exception IOException for zero-length replys from
		  * xastir or for unrecognized replies. */
	private int send(DatagramPacket packet) throws IOException { 
		socket.send(packet);
		socket.receive(receivePacket);
		int reclen = receivePacket.getLength();
		int answer = -1;
		if (reclen > 0) {
			String reply = new String(readBuffer, 0, reclen);
			if (reply.startsWith(XASTIR_ACK_LINE)) {
				answer = XASTIR_ACK;
			} else if (reply.startsWith(XASTIR_NACK_LINE)) {
				answer = XASTIR_NACK;
			} else {
				throw new IOException("xastir reply not recognized: " + reply);
			}
		} else {
			throw new IOException("xastir reply zero length");
		}
		return answer;
	}

	private static final void printUsage() {
		System.err.println("arguments: host port callsign passcode"
			+ " [ -to_igate ] [ -to_rf ] APRS_PACKET");
	}

}
